####  QUICK 'N DIRTY GAME LIBRARY  ####
#--------------------------------#
#These are a bunch of things I use in almost all games I make.
#You can either import this module, or copy/paste the functions you want
#to your game so they are easier to edit and can make use of global
#variables for convenience
#
#PS: This is released under the MIT license

import pygame, os
from pygame.locals import *

####CLASSES####
class Empty:
	"""	An empty class that can be handy for storing data."""
	def __init__(self, **args):
		pass
class InputHandler:
	"""
	Handles keyboard and mouse states. Has lists  keys and mousebuttons
	and variables mousepos, mousex and mousey."""
	def __init__(self):
		self.keys = []
		for i in range(512):
			self.keys.append(False)
		self.mousepos = self.mousex, self.mousey = 0,0
		self.mousebuttons = [False,False,False,False,False,False,False]
	def update(self, updateMouse = True):
		"""Call this to update key and mouse states."""
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				exit()
			elif event.type == pygame.KEYDOWN:
				self.keys[event.key] = True
				self.key_pressed(event.key)
				continue
			elif event.type == pygame.KEYUP:
				self.keys[event.key] = False
				continue
			elif event.type == pygame.MOUSEBUTTONDOWN:
				self.mousebuttons[event.button] = True
				self.mouse_pressed(event.button)
				continue
			elif event.type == pygame.MOUSEBUTTONUP:
				self.mousebuttons[event.button] = False
				continue
		self.mousepos = self.mousex,self.mousey = pygame.mouse.get_pos()
	def key_pressed(self,key):
		"""
		Called when a key is pressed. Use this if you make a class
		extending InputHandler.
		"""
		pass
	def mouse_pressed(self,button):
		"""
		Called when a mouse button is pressed. Use this if you make a 
		class extending InputHandler.
		"""
		pass
class Color:
	"""Stores rgb tuples with names"""
	BLACK = (0,0,0)
	WHITE = (255,255,255)
	RED = (255,0,0)
	GREEN = (0,255,0)
	BLUE = (0,0,255)
	YELLOW = (255,255,0)
	CYAN = (0,255,255)
	PURPLE = (255,0,255)
class Camera:
	"""
	The camera requires a surface to be attatched to; most likely you
	want this to be your screen. Optionally, the maximum coordiates
	can be passed so the camera does no scroll off the edge of the
	screen.
	"""
	def __init__(self,screen,x=0,y=0,maxx=0,maxy=0,z=1):
		self.screen = screen
		self.x = x
		self.y = y
		self.z = z
		self.maxx = maxx-1
		self.maxy = maxy-1
		self.screenWidth = screen.get_width()
		self.screenHeight = screen.get_height()
		print "max:", self.maxx, self.maxy
	def update(self, screen, fieldSize = None):
		self.screen = screen
		self.screenWidth = screen.get_width()
		self.screenHeight = screen.get_height()
		if fieldSize != None:
			self.maxx = fieldSize[0]-1
			self.maxy = fieldSize[1]-1
	def move(self, x=None,y=None, relative=False, z=None):
		"""
		Moves the camera to the given coordinate. Does not require
		both x and y.
		"""
		if relative:
			if x!= None:
				x = self.x + x
			if y!= None:
				y = self.y + y
			if z!= None:
				z = self.z + z
		if x!=None:
			if x > 0:
				if x+self.screenWidth>self.maxx:
					self.x = self.maxx-self.screenWidth
				else:
					self.x = x
			else:
				self.x = 0
		else:
			self.x = 0
		if y!=None:
			if y > 0:
				if y+self.screenHeight>self.maxy:
					self.y = self.maxy-self.screenHeight
				else:
					self.y = y
			else:
				self.y = 0
		else:
			self.y = 0
		if z!=None:
			self.z=z
	def center(self, (x,y)):
		"""Centers the camera on the given (x,y) coordinate."""
		if x-self.screenWidth/2 > 0:
			if x+self.screenWidth/2 > self.maxx:
				self.x = self.maxx-self.screenWidth
			else:
				self.x = x-self.screenWidth/2
		else:
			self.x = 0
		if y-self.screenHeight/2 > 0:
			if y+self.screenHeight/2 > self.maxy:
				self.y = self.maxy-self.screenHeight
			else:
				self.y = y-self.screenHeight/2
		else:
			self.y = 0
		self.x = int(self.x)
		self.y = int(self.y)
	def centerSmooth(self, (x,y), speed=2, moveMouse = None):
		center = self.x+self.screenWidth/2,self.y+self.screenHeight/2

		distance = math.hypot(self.x-x,self.y-y)

		dx = int((x - center[0])/speed)
		dy = int((y - center[1])/speed)

		if abs(dx) < 10:
			dx /= 2
		if abs(dy) < 10:
			dy /= 2

		newx = center[0] + dx #((self.x+self.screenWidth/2)*speed+x)/(speed+1)
		newy = center[1] + dy #((self.y+self.screenHeight/2)*speed+y)/(speed+1)

		self.center((newx,newy))
		if moveMouse != None:
			if self.x != 0 and self.y == 0:
				pygame.mouse.set_pos((moveMouse.screenx-dx,moveMouse.screeny))
			elif self.x == 0 and self.y != 0:
				pygame.mouse.set_pos((moveMouse.screenx,moveMouse.screeny-dy))
			if self.x != 0 and self.y != 0:
				pygame.mouse.set_pos((moveMouse.screenx-dx,moveMouse.screeny-dy))

####FUNCTIONS####
def draw_text(targetSurface, text, (x,y), font, color = (255,255,255), alpha=None):
	"""
	Draws text on the targetSurface at pos in font and color. Defaults 
	to white. Substitute "c" for a coordinate to center on that axis.
	Alpha support is in development. Currently, the font becomes aliased
	when alpha drawing is enabled for the font.
	 """
	text = str(text)
	if "c" not in (x,y):
		textSurface = font.render(text, True, color)
		targetSurface.blit(textSurface, (x,y))
		return
	else:
		width,height = font.size(text)
		surfaceWidth, surfaceHeight = targetSurface.get_size()
		if x == "c":
			x = surfaceWidth/2-width/2
		if y == "c":
			y = surfaceHeight/2-height/2
	if alpha == None:
		textSurface = font.render(text, True, color)
		targetSurface.blit(textSurface, (x,y))
		return
	font.render(text, False, color)
	textSurface.set_alpha(alpha)
	targetSurface.blit(textSurface, (x,y))
def load_image(name, folder = ''):
	"""Load and convert an image. Returns the image handle."""
	if type(folder) == str:
		fullname = os.path.join(folder, name)
	else:
		for directory in folder:
			name = os.path.join(directory, name)
		fullname = name
	try:
		image = pygame.image.load(fullname)
		if image.get_alpha() is None:
			image = image.convert()
		else:
			image = image.convert_alpha()
	except pygame.error, message:
		print 'Cannot load image:', fullname
		raise SystemExit, message
	return image
def load_tileset(file,folder = '', tileWidth=32,tileHeight=32):
	"""
	Creates a tileset out of the given image. Requires load_image().
	"""
	image = load_image(file, folder)
	width = image.get_width()
	height = image.get_height()
	tileset = []
	for i in range(width/tileWidth):
		for j in range(height/tileHeight):
			tile = pygame.Surface((tileWidth,tileHeight))
			tile.blit(image,(0,0),(tileWidth*i,tileHeight*j,tileWidth,tileHeight))
			tileset.append(tile)
	return tileset
def load_sound(name, folder = ''):
	"""Load a sound and returns pygame Sound object handle"""
	if not pygame.mixer: #Test for sound capabilities
		print "pygame.mixer not functioning"
		exit()
	if type(folder) == str:
		fullname = os.path.join(folder, name)
	else:
		for directory in folder:
			name = os.path.join(directory, name)
		fullname = name
	try:
		sound = pygame.mixer.Sound(fullname)
	except pygame.error:
		print ('Cannot load sound:', fullname)
		raise SystemExit, message
	return sound
def load_tilemap(file, folder = ''):
	"""
	Loads and returns a tilemap from the given file in the given folder
	"""
	print "Loading Map", file
	file = open(os.path.join(folder, file), "r")
	lines = file.readlines()
	file.close()

	if "," not in lines[0]:
		tilemap = [map(int, line.split()) for line in lines]
		return tilemap
	
	print "tilemap in CSV format, you should convert it"
	tilemap = []
	file.close()
	currentLine = 1
	totalLines = len(lines)
	for line in lines:
		row = line.replace("\n","").split(",")
		newRow = []
		for i in row:
			newRow.append(int(i))
		tilemap.append(newRow)

		if currentLine%32 == 0:
			print "Loaded" + str(currentLine)+"/"+str(totalLines)
		currentLine += 1
	print "Map Loaded"
	return tilemap
def save_tilemap(tilemap, file, folder = 'maps'):
	"""
	Saves the given tilemap to a the file in the given folder
	(defaults to maps)
	"""
	print "Saving Map..."
	filename = os.path.join(folder, file)
	file = open(filename, "w")

	currentLine = 1
	totalLines = len(tilemap)

	for row in tilemap:
		newRow = []
		for tile in row:
			newRow.append(str(tile))
		file.write(" ".join(newRow)+"\n")

		if totalLines > 64 and currentLine%32 == 0:
			print "Saved "+str(currentLine)+"/"+str(totalLines)
		currentLine += 1
	file.close()
	print "Saved to: " + filename
def draw_tiles(targetSurface, camera, tilemap, tileset, tileWidth=32,tileHeight=32):
	"""
	Draws the background tiles using given tileset and tilemap relative 
	to the camera. Assumend tile size is 32x32, though can be set with the
	tileWidth and tileHeight parameters.
	"""
	windowWidth,windowHeight = targetSurface.get_size()
	startx = int(camera.x/tileWidth)
	endx = startx + int(windowWidth/tileWidth)+1
	starty = int(camera.y/tileHeight)
	endy = starty + int(windowHeight/tileHeight)+1
	for i in range(startx,endx):
		for j in range(starty,endy):
			targetSurface.blit(tileset[tilemap[i][j]], (i*tileWidth-camera.x,j*tileHeight-camera.y))
def draw_image(image, targetSurface, (x,y), angle = 0, scale = 1.0, smooth = False, camera = None):
	"""
	Draws image on the targetSurface at position (x,y). Rotates the
	image by angle degrees counter-clockwise. Also scales image by a
	floating point muliplier that is scale. If smooth is on, it will use
	a smooth scaling filter. Keep in mind that scaling and rotating take
	up quite a bit of processing, and smooth scaling much more so. An
	optional camera object can be passed to specify drawing relative to
	that camera.
	"""
	if camera != None:
		x = x-camera.x
		y = y-camera.y
	if angle == 0 and scale == 1.0:
		targetSurface.blit(image,(x,y))
		return
	elif angle == 0:
		if not smooth:
			targetSurface.blit(pygame.transform.scale(image, (int(image.get_width()*scale),int(image.get_height()*scale))),(x,y))
			return
		else:
			targetSurface.blit(pygame.transform.smoothscale(image, (int(image.get_width()*scale),int(image.get_height()*scale))),(x,y))
			return
	elif scale == 1.0:
		targetSurface.blit(pygame.transform.rotate(image, angle),(x,y))
		return
	else:
		if not smooth:
			targetSurface.blit(pygame.transform.rotozoom(image, angle, scale),(x,y))
			return
		else:
			targetSurface.blit(pygame.transform.rotate(pygame.transform.smoothscale(image, (int(image.get_width()*scale),int(image.get_height()*scale))),angle),(x,y))

#debugging
if __name__ == '__main__':
	pygame.init()
	clock = pygame.time.Clock()
	screen = pygame.display.set_mode((500,500))
	input = InputHandler()
	camera = Camera(screen)
	mainfont = pygame.font.Font(None,22)
	while True:
		screen.fill((100,100,100))
		input.update()
		for i,key in enumerate(input.keys):
			if key:
				draw_text(screen,str(i),((25*i)%400+50,(i/16)*26+50),mainfont,(255,255,255))
		pygame.display.flip()
		clock.tick(30)
	
